תרגול 3 ניתוח לשיעורין תאריך עדכון אחרון: 27 בפברואר 2011. ניתוח לשיעורין analysis) (amortized הוא טכניקה לניתוח זמן ריצה לסדרת פעולות, אשר מאפשר קבלת חסמי זמן ריצה נמוכים יותר מאשר חסמים המתקבלים כאשר מניחים את המקרה הגרוע ביותר בכל פעולה. בדרך כלל אנחנו מתייחסים למספר פעולות, כאשר חלק מהפעולות יקרות יותר, וחלקן זולות יותר. בניתוח לשיעורין אנו מדברים על עלות כל פעולה בממוצע, עבור כל רצף של פעולות אפשרי של שימוש במבנה הנתונים. אנו מדברים על המקרה הגרוע ביותר; כלומר, לוקחים בחשבון את הסידרה הגרועה ביותר של פעולות שיכולה להיות. ניתוח לשיעורין היא איזושהי אסטרטגיה לניתוח כל רצף של פעולות המראה שממוצע העלות לכל פעולה הוא קטן, למרות שישנן פעולות יחידות בתוך הרצף שעלולות להיות יקרות. נעיר שלמרות שאנו מדברים על ממוצעים, אנו לא מערבים הסתברויות בניתוח. המטרה היא חישוב חסם עליון הדוק ככל שניתן לעלות של רצף כלשהו של n פעולות. נניח עלות הפעולה ה.T(n)/n אזי עלות כל פעולה בממוצע היא. n c i המטרה היא לחשב,c i היא i נראה שלוש שיטות לניתוח לשיעורין שיטת הצבירה, שיטת החיובים (שיטת ה"בנק")ושיטת הפוטנציאל. שיטת הפוטנציאל היא החשובה ביותר. 3.1 דוגמא מחסנית נתבונן במחסנית התומכת בפעולות הבאות: (1)O. למחסנית. עלות x מכניס את push(x) (1)O. להוציא את האיבר האחרון שנכנס (אם יש כזה). עלות pop(x) multi pop(x) מוציאה את כל האיברים מהמחסנית. עלות: אם יש k איברים כרגע במחסנית אזי.O(k) נממש (בפסאדו קוד) את :multi pop() multi-pop() while not isempty() pop() נשים לב שכל הפעולות עולות (1)O, מלבד הפעולה.multi pop בפרט, הפעולה הנ"ל נראית יקרה היא לינארית במספר האיברים במחסנית. כך, אם ישנם n איברים במחסנית, עלותה O(n) (מה שנשמע רע מאוד ביחס ל (1)O). אבל האם אנחנו לא מחמירים מדי כשאנו מנתחים את הפעולה בצורה כזו? כדי לחדד את השאלה, נתבונן בניתוח השגוי הבא. נשאל עבור סידרה של n פעולות מהי העלות worst case לסידרה. כעת, יכולים לטעון (בטעות) שכל פעולה מסוג multi pop עולה O(n) בכל פעם, ולכן בסה"כ רצף של n פעולות עלול להגיע ל ) 2.O(n אך, ניתוח זה לא הדוק. 13
אצלינו, בכדי שיהיו n איברים במחסנית, צריכים להיות n פעולות push לפני כן. אם נתונים לנו רצף של פעולות מסוימות, נניח n פעולות push ולאחריהן פעולת multi pop עבור n פעולות ה push אנחנו משלמים n בסה"כ, ועל פעולת multi pop אנו משלמים n. כלומר בסה"כ אנו משלמים עבור רצף + 1 n הפעולות 2n, מה שאומר שבממוצע לכל פעולה אנו משלמים 2. כלומר, העלות הממוצעת לכל פעולה היא (1)O. כעת נראה שיטות ואסטרטגיות לחישוב של ניתוח לשיעורין. 3.2 שיטת הצבירה בשיטת הצבירה אנו מראים שלכל n, סדרה של n פעולות צורכת זמן של T(n) לכל היותר. נקבל אם כן, שממוצע זמן של כל פעולה, או העלות לשיעורין של כל פעולה, הינה.T(n)/n זוהי העלות לשיעורין של כל פעולה גם כאשר הסידרה מכילה סוגים שונים של פעולות. בדוגמת המחסנית ננתח כמה עולה n פעולות על המחסנית. יש שלוש פעולות אפשריות:,pop,push.multi pop אמנם multi pop עולה O(n) במקרה הגרוע, אך נשים לב שכל עצם ניתן לשלוף רק פעם אחת עבור כל פעם שהוא נדחף למחסנית. לכן, מספר הקריאות ל pop (גם אלו שבתוך המימוש של (multi pop הוא לכל היותר מספר הקריאות ל.push מכיוון שאנו מתבוננים בסידרה מאורך n, אין יותר מ n קריאות ל,push ולפיכך מספר הקריאות הכולל ל pop (גם אלו שבתוך (multi pop הוא לכל היותר n. נקבל כי העלות הכוללת היא לכל היותר 2n, ולכן בממוצע עלות כל פעולה היא 2, כלומר (1)O. 3.2.1 יותר פורמלי בשיטת הצבירה אנו מחשבים סכום כל העלויות עבור סידרה כללית של פעולות. מכיוון שאנו מתבוננים על סידרה כללית, הניתוח יכסה גם את הסידרה הגרועה ביותר. בכדי לדבר על סידרה כלשהי נתבונן בסידרה כללית של פעולות: S = (σ 1,σ 2,...,σ n ) כל פעולה {1,2,3} i σ, כאשר 1 מציין את הפעולה,push 2 מציין,pop ו 3 מציין.multi pop אם עלות הפעולה ה i היא c, i נרצה לחשב: k=1 c k נתבונן בכל המקומות בסידרה כך ש = 3 i.(multi pop) σ נניח שכל הפעולות הנ"ל נמצאות במקומות } t,i = {i 1,...,i וכך ש.i 1 < i 2 <... < i t (נעיר שאם I ריקה, אזי T(n) הוא סכום של קבועים (אין פעולת i. 0 בנוסף, נגדיר כי = 0 n). ולכן הסכום כולו לינארי ב (multi pop נסמן את סידרת הפעולות של S בין המקומות 1 j i ובין i j ב S. ij כלומר, הסידרה: S ij = {σ ij 1 +1,...,σ ij } כעת, נראה את הטענה הבאה: טענה 3.1 לכל j t,1 עלות הפעולה multi pop הנמצאת במקום i j (כלומר, עלות הפעולה (σ ij הינה.2(i j i j 1 1) הינה S ij לכל היותר. בנוסף, עלות הסידרה i j i j 1 1 הוכחה: יהי j נתון. לאחר הפעולה ij 1 σ המחסנית ריקה (שכן, פעולה זו הייתה פעולת.(multi pop כל שאר הפעולות אינן פעולות multi pop (חתכנו את סדרת הפעולות בין זוג פעולות.(multi pop לכן, לכל פעולה בסדרה הנ"ל, הפעולה היא push או.pop במקרה הגרוע ביותר כל הפעולות הן,push מה שאומר שכאשר 14
מגיעים לפעולה σ ij (פעולת ה (multi pop ישנם 1 j 1 i j i איברים במחסנית. לפיכך, העלות של פעולת ה multi pop היא j 1 1.i j i לכן, העלות הכוללת לסידרה S ij היא לכל היותר 1) j 1.2(i j i כעת, לכל סידרה S ניתן להתייחס ל } t I. = i} 1 i,..., נקבל: t T(n) 2(i j i j 1 1)+ c j = 2(i t i 0 ) 2t+(n i t ) j=1 j=i t+1 נזכור ש i t הינו איזשהו אינדקס בתחום i t n 1, וכמו כן i 0 הינו 0. לפיכך: 2(i t i 0 ) 2t+(n i t ) = 2i t 2t+n i t = n+i t 2t 2n 2t 2n כלומר, הצלחנו להראות שלכל סידרה של פעולות מאורך n, העלות הכוללת היא לכל היותר 2n. הנ"ל מראה שעלות ממוצעת של כל פעולה היא (1)O. 3.3 שיטת החיובים ("שיטת הבנק") בשיטה זו ניתן לכל פעולה עלות שונה מזו שהיא עולה באמת. עלות זו נקראת "עלות לשיעורין". חלק מהפעולות יקבלו עלות גדולה יותר מהעלות האמיתית שלהן; חלק מהפעולות יקבלו עלות קטנה יותר מזו שהן עולות בפועל (בדרך כלל, הפעולה היקרה ביותר בפועל תקבל את העלות הקטנה ביותר לשיעורין). העיקרון שצריך תמיד לשמור עליו, הוא שסידרה של n פעולות (לכל n) לפי העלויות לשיעורין שלנו תהיה גדולה יותר מהעלות האמיתית שלהן בפועל. נשתמש ב"בנק" בשביל "לשמור" לנו עלויות. נניח שעל פעולת push אנחנו משלמים 2 יחידות במקום יחידה אחת; יחידה אחת תהיה העלות האמיתית של הפעולה (שהיינו צריכים לשלם ממילא), והיחידה השנייה פשוט תכנס ל"בנק" בשביל "התחשבנות עתידית". כעת, כאשר נבצע פעולת multi pop לא נשלם מ"הכיס" אלא נמשוך את העלויות שצברנו בבנק; מכיוון שעל כל איבר שיש כרגע במחסנית כבר צברנו יחידה אחת בבנק, נקבל כי יש בבנק מספיק כסף בשביל לממן את הפעולה,multi pop ולכן לא נשלם עבור פעולה זו מהכיס בכלל. הסבר נוסף כאשר אני מכניס איבר למחסנית (ומשלם עליו 1), אני משלם גם עבור ההוצאה שלו כבר בזמן ההכנסה (עוד יחידה אחת). לכן, בסה"כ הכנסה עולה 2 יחידות; כעת, הוצאה יחידה, או הוצאה ע"י multi pop הן למעשה בחינם (שילמנו עליהן קודם; בבנק יש מספיק כסף בשביל לשלם על ההוצאה שלהן). יותר פורמלי. נסמן ב ĉ i את העלות לשיעורין של הפעולה ה i. נסמן ב c i את העלות האמיתית של הפעולה ה i. העלות לשיעורין של הפעולה ה i בשיטת הבנק הינה: ĉ i = c i +deposit withdraw כלומר עלות הפעולה היא העלות האמיתית + ההפקדה שאנו מבצעים לבנק, פחות העלות של המשיכה (במידת הצורך). כאשר אנו שומרים על העיקרון שלעולם "לא ניכנס למינוס" (לעולם לא נמשוך מהבנק סכום כסף שלא הפקדנו אותו לפני כן), מתקיים שסך כל ההפקדות (סכום כל הערכים של (deposit גדול מכל הפעמים שמשכנו מהבנק (סכום כל הערכים שביצענו,(withdraw ולכן: ĉ i c i = T(n) כלומר, העלות האמיתית של n הפעולות היא קטנה מסכום העלות לשיעורין. כעת, ננתח כל אחת מפעולות המחסנית: 15
עבור פעולת push עלות הפעולה היא 1, בנוסף, מכניסים 1 לבנק. נקבל: ĉ i = 1+1 0 = 2 עבור פעולת pop עלות הפעולה היא 1. לא מפקידים לבנק, ולא מושכים ממנו. 1 נקבל: ĉ i = 1+0 0 = 1 עבור פעולת multi pop נניח שמספר האיברים כרגע הוא k. עלות הפעולה אם כן היא k. כעת, נמשוך מהבנק k יחידות (בדוק שהבנת מדוע אנו בטוחים כי בבנק יש k יחידות). נקבל: ĉ i = k +0 k = 0 אם כן, העלות לשיעורין של כל פעולה קטן מ 2. מכיוון שלעולם לא נכנסים למינוס, נקבל כי: ĉ i c i = T(n) T(n) ĉ i 2n אצלינו, לכל.ĉ i 2,i כלומר: ולכן סכום התשלומים עבור n פעולות קטן מ 2n. כלומר, העלות הממוצעת לכל פעולה היא 2. חשוב לציין שכאשר מבצעים ניתוח בשיטת הבנק חייבים תמיד לשמור על האינווריאנטה שסכום הכסף בבנק הוא חיובי. אסור בשום אופן "להכנס למינוס", שכן כאשר אנו "נכנסים למינוס" העלות האמיתית של הפעולות היא פחות מהעלות לשיעורין שאנו מבצעים, ואז לא נוכל לטעון שהעלות הכוללת של העלויות לשיעורין גדולה מהעלויות האמיתיות. 3.4 שיטת הפוטנציאל שיטת הפוטנציאל דומה מאוד לשיטת הבנק, רק שאנו ממדלים אותה באופן יותר מתמטי, ולכן היא יותר פורמלית (ופחות אינטואיטיבית..). בשיטת הבנק "התשלום מראש" היה שמור כיתרה לזכותם של עצמים ספציפיים במבנה הנתונים (כל איבר שמר לעצמו את העלות להוצאתו בעתיד). כאן, ה"תשלום מראש" מובע בעזרת "אנרגיה פוטנציאלית" שניתן לשחרר כדי לשלם עבור פעולות בעתיד. כמו בשיטת הבנק פעולת push תגדיל לנו את הפוטנציאל של מבנה הנתונים (ונשלם על הגדלה זו), ופעולת multi pop תשולם בעזרת "פירוק הפוטנציאל הזה", או, ריקון הפוטנציאל (ואין צורך לשלם עליה כי כבר שילמנו על הפוטנציאל עצמו). 1 יכולנו להגדיר שגם במקרה זה אנו מושכים את העלות מהבנק, ולא משלמים מהכיס. במקרה זה נקבל כי העלות לשיעורין היא 0. נציין שעדיין לא נכנס למינוס, וכל המשך הניתוח הוא בדיוק אותו הדבר. 16
שיטתהפוטנציאל. יהי D 0 המצב ההתחלתי של מבנה הנתונים שעליו מבוצעות n פעולות. נסמן ב c i את העלות של הפעולה ה D i i. הינו המצב של המערכת לאחר הפעולה ה i, על המצב 1 i D. כלומר, מתחילים ממצב ומגיעים למצב D, 2 σ) 2 ) לאחר מכן מבצעים פעולה כלשהי ומגיעים למצב D. 1 σ) 1 ) מבצעים פעולה כלשהי D, 0 וכן הלאה. פונקציית הפוטנציאל φ ממפה כל מצב של מבנה הנתונים D i למספר ממשי שמציין את הפוטנציאל המיוחס למבנה הנתונים. נגדיר את העלות לשיעורין של הפעולה ה i כ: ĉ i = c i +φ(d i ) φ(d i 1 ) כלומר, העלות לשיעורין של הפעולה ה i היא העלות האמיתית + c i הפרש הפוטנציאלים של מבנה הנתונים. במילים אחרות, העלות האמיתית של הפעולה ה i הינה: העלות של כל סידרה כלשהי של n פעולות: c i = ĉ i +φ(d i 1 ) φ(d i ) = = c i = [ĉ i +φ(d i 1 ) φ(d i )] ĉ i + [φ(d i 1 ) φ(d i )] ĉ i +φ(d 0 ) φ(d n ) כאשר הצעד האחרון נכון מכיון שהטור הינו טלסקופי. אם תמיד יתקיים ) n,φ(d 0 ) φ(d נקבל כי : ĉ i +φ(d 0 ) φ(d n ) ĉ i c i ĉ i כלומר, גם פה, כמו בשיטת הבנק, אנו שומרים על כך שסכום עלות n פעולות קטן יותר מסכום ה"עלות לשיעורין". לכן, הניתוח הופך לפשוט יותר: כל שצריך הוא לחשב את ĉ i לכל אחת מהפעולות הקיימות על מבנה הנתונים. ניתוח מחסנית בעזרת שיטת הפוטנציאל. נגדיר את פונקציית הפוטנציאל ) i φ(d כמספר האיברים שיש כרגע (= מצב D) i במחסנית. נקבל: φ(d 0 ) = 0 ובנוסף: 1 i n,φ(d i ) 0 כלומר, פונקציית פוטנציאל זו טובה ועונה על הדרישה. הפיתוחהנ"לאומרלנושבכדילהראותשהעלותהממוצעתשלכלפעולהבכלסדרההינוקבוע, מספיקלהראות ש ĉ קבוע. לכן, נחשב עבור כל אחד מסוגי הפעולות. 17
עבור פעולת push עלות הפעולה הוא 1, ונניח שהיו t איברים במחסנית. לאחר הפעולה, ישנם + 1 t איברים במחסנית. נקבל: ĉ i = c i +φ(d i ) φ(d i 1 ) = 1+(t+1) t = 2 עבורפעולתpop עלותהפעולההוא 1, ונניחשהיוt איבריםבמחסנית. לאחרהפעולה, ישנםt 1 איברים במחסנית. נקבל: ĉ i = c i +φ(d i ) φ(d i 1 ) = 1+t 1 t = 0 עבור פעולת.multi pop נגיד שיש כרגע t איברים במחסנית. אזי, עלות הפעולה הוא t, ומצד שני הפרש הפוטנציאלים הוא t (לפני הפעולה היו t איברים במחסנית, ולאחריה 0). נקבל אם כן: ĉ i = c i +φ(d i ) φ(d i 1 ) = t+0 t = 0 c i ĉ i 2 = 2n נקבל: ולכן העלות הממוצעת של כל פעולה 2. 3.5 מונה בינארי נניחישלנומונהעם k ביטים, הסופרבבינארית. אנחנולמעשהתומכיםרקבפעולהאחת increment העלהב 1. עלות כל פעולה = מספר הביטים שהוחלפו. לדוגמא: (לצד כל ערך בינארי מצויין עלות פעולת ה (increment 00000 0 00001 1 00010 2 00011 1 00100 3 00101 1 00110 2. כמה עולה הפעולה?increment כמה עולה רצף של n פעולות? שוב, ניתוח שגוי יאמר כי בכל פעם, במקרה הגרוע ביותר אנו מבצעים O(k) החלפות (שכן יש k ביטים במונה. במקרה הגרוע ביותר נחליף את כל הביטים במונה). ולכן, במקרה הגרוע ביותר (n.o(k ניתוח זה שגוי. נבצע ניתוח לשיעורין. 18
3.5.1 ניתוח לפי שיטת הצבירה במקרה זה, מכיוון שישנה רק פעולה אחת (increment) הניתוח לפי שיטת הצבירה פשוט. כל רצף של פעולות נראה בדיוק אותו הדבר. נתבונן על n פעולות. הביט הראשון יתחלף n פעמים (בכל פעם). הביט השני לעומת זאת, יתחלף בכל פעם i פעמים. הביט ה פעמים. הביט השלישי יתחלף כל פעם רביעית, כלומר בסה"כ ב n 4 n 2 זוגית, כלומר ב יתחלף n פעמים. כלומר: 2 i 1 number of bits that were changed k [ ] = number of times that the i th bit was changed = k n k 2 i 1 = n 1 2n 2i 1 כלומר, 2.T(n)/n 3.5.2 ניתוח לפי שיטת הפוטנציאל נגדיר פונקציאת פוטנציאל: ) i = φ(d מספר האחדות במונה. 2 מתקיים: = 0 ) 0,φ(D ולכל i n 1 נקבל: 0 ) i,φ(d ולכן פונקציית פוטנציאל זו עונה על הדרישה שלנו. נראה כמה עולה פעולת.increment כאשר מבצעים פעולת,increment האפס הראשון מימין הופך ל 1, וכל שאר האחדות מימין אליו מתאפסים. נניח שישנם t אחדות רצופים מסוף המונה (מימין). במצב כזה, עלות פעולת increment תהיה + 1 t (שכן נשנה את t האחדות לאפסים, ואת האפס שלאחריהם ל 1). מצד שני, נשים לב שכעת יש פחות 1 t אחדות בביטוי (שכן t אחדות הפכנו לאפסים, ואפס אחד הפכנו ל 1). נקבל: ĉ i = c i +φ(d i ) φ(d i 1 ) = t+1 (t 1) = 2 לכן, רצף של n פעולות יעלה פחות מ 2n. כלומר, הממוצע לכל פעולה 2. 3.6 מערך דינאמי המערךתומךבפעולת insert ומכניסאת האיברלמקוםהפנויהבא. נניחשבמערךיש n מקומות, ונניחשהמערך מלא. כעת, כאשר נבקש להוסיף איבר נוסף, נבצע את הפעולות הבאות: נקצה מערך בגודל 2n. נעתיק את n האיברים הישנים. נוסיף את האיבר החדש במקום 1+n. הנחה: זמן הקצאה: (1)O. שוב, נעיר שניתוח של n פעולות יכול להיות שגוי במקרה הגרוע ביותר, insert עולה,O(n) ולכן n פעולות יעלה ) 2.O(n שוב, הניתוח הנ"ל שגוי מכיוון שכדי באמת להגיע לפעולה שעולה,O(n) צריך לבצע קודם לכן n פעולות שעולות (1)O כל אחת, ולכן ממוצע כל פעולה הוא קבוע. ננתח בעזרת שיטת הפוטנציאל בלבד. 2 במדעי המחשב, הפונקציה שסופרת את מספר האחדות בביטוי בינארי נקראת "משקל המינג". 19
בשיטת הפוטנציאל, ננסה לתת מוטיבציה לבחירת פונקציאת הפוטנציאל. ככל שיש יותר איברים במערך הסיכוי שנצטרך להעתיק אותו למערך חדש הוא גדל. "הפוטנציאל" המצטבר במערכת הוא יותר גדול, ולכן נרצה לשלם על כך מראש (כדי שפירוק הפוטנציאל יהיה "בחינם"). אם כן, נרצה לבדוק עד כמה אנו קרובים לנקודת פירוק הפוטנציאל. לשם כך נשתמש בשני משתנים num ו num.size ישמור את מספר האיברים שיש כרגע במערך. size ישמור את אורך המערך. נשים לב שכאשר size = num בהכנסה הבאה נצטרך לפרק את הפוטנציאל. כלומר, כאשר size+1 num = נצטרך להגדיל את size פי שתיים. בנוסף, כאשר size 2 num נקבל כי הגדלנו את מבנה הנתונים פי שתיים רק זה מכבר, ונרצה שבנקודה זו ערך הפוטנציאל יהיה 0. ככל שיכניסו יותר איברים ערך הפוטנציאל יעלה, עד אשר נגיע למצב.num = size כאן נרצה שערך הפוטנציאל יהיה מספיק בשביל לשלם עבור ההרחבה הבאה. פונקציית פוטנציאל שמתארת את הדרישות שלנו היא: φ(d i ) = 2 num size מכיוון שהמערך תמיד מלא בלפחות חצי מהאיברים 2num) (size נקבל כי לכל φ(d i ) i חיובי. בנוסף, = 0 ) 0.φ(D כלומר, פונקציה זו עונה על הדרישה. כלומר, אם גודל המערך הוא 16, ויש לנו 9 איברים, ערך הפוטנציאל הוא 2. כאשר נגיע ל 16 איברים, ערך הפוטנציאל יהיה כבר 16, ופירוקו ישלם לנו עבור ההעתקה למערך החדש. כעת נחשב את העלות לשיעורין. נחלק לשני מקרים: אם הפעולה ה i לא גורמת להרחבת מבנה הנתונים, נקבל כי i 1 c i = 1,size i = size ו = i num i 1 +1.num לכן: ĉ i = c i +φ(d i ) φ(d i 1 ) = 1+2 num i size i (2 num i 1 size i 1 ) = 1+2 (num i 1 +1) size i 2 num i 1 size i 1 = 1+2+2 num i 1 2 num i 1 = 3 אם הפעולה ה i כן גורמת להרחבת מבנה הנתונים, נקבל כי 1 i size i = 2 size (אנו מגדילים את גודל המערך פי שניים). כמו כן, 1 i 1+ c i = num i = num העלות של הפעולה ה i היא העתקת 1 i num איברים, והכנסת איבר נוסף אחד. מספר האיברים כעת הוא 1 i 1+.num i = num לבסוף, מכיוון שהמערך הועתק נקבל כי המערך היה מלא. כלומר 1 i.num 1 i = size אם כן: ĉ i = c i +φ(d i ) φ(d i 1 ) = num i +2 num i size i (2 num i 1 size i 1 ) = 3 num i size i 2 num i 1 +size i 1 = 3 num i 2(num i 1) 2 size i 1 +size i 1 = 3 num i 2 num i +2 size i 1 = num i size i 1 +2 = 1+2 = 3 שכן, מספר האיברים כרגע במערך ) i (num גדול מאחד מגודל המערך לפני ההגדלה. 20
קיבלנו כי לכל סוג של פעולה 3 ĉ, ולכן, העלות הכוללת של כל סידרת פעולות היא: c i ĉ i 3 = 3n ולכן, העלות הממוצעת לכל פעולה היא = 3.T(n)/n 3.7 לקריאה נוספת קורמן, לייזרסון, ריבסט: "מבוא לאלגוריתמים", הוצאת M. IT תורגם לעברית ע"י האוניברסיטה הפתוחה. 21